package cn.lg.soar.message.service.impl;

import cn.lg.soar.common.data.msg.I18n;
import cn.lg.soar.common.model.PageQuery;
import cn.lg.soar.common.model.PageResult;
import cn.lg.soar.common.model.SimplePageResult;
import cn.lg.soar.common.util.AssertUtil;
import cn.lg.soar.common.util.PojoUtil;
import cn.lg.soar.common.util.current.CurrentProxy;
import cn.lg.soar.common.util.current.UserContext;
import cn.lg.soar.common.util.jackson.JacksonUtil;
import cn.lg.soar.database.base.service.impl.BaseServiceImpl;
import cn.lg.soar.database.query.QueryParams;
import cn.lg.soar.database.query.SqueryWapper;
import cn.lg.soar.database.util.PageUtils;
import cn.lg.soar.message.api.enums.NoticeRecipientTypeEnum;
import cn.lg.soar.message.api.enums.NoticeStatusEnum;
import cn.lg.soar.message.entity.Notice;
import cn.lg.soar.message.entity.NoticeRecipient;
import cn.lg.soar.message.event.WebSocketOpenEvent;
import cn.lg.soar.message.mapper.NoticeMapper;
import cn.lg.soar.message.model.NoticeDetailVo;
import cn.lg.soar.message.model.NoticeVo;
import cn.lg.soar.message.service.INoticeRecipientService;
import cn.lg.soar.message.service.INoticeService;
import cn.lg.soar.message.service.SseService;
import cn.lg.soar.message.service.WebSocketService;
import cn.lg.soar.mvc.event.SseEvent;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class NoticeServiceImpl extends BaseServiceImpl<NoticeMapper, Notice> implements INoticeService, CurrentProxy<NoticeServiceImpl> {

    @Autowired
    private INoticeRecipientService noticeRecipientService;
    @Autowired
    private WebSocketService webSocketService;
    @Autowired
    private SseService sseService;

    @Override
    public void broadcast(Notice notice) {
        notice.setRecipientType(NoticeRecipientTypeEnum.all);
        AssertUtil.isTrue(save(notice), I18n.build("通知失败"));
        webSocketService.sendMsgAllAsync("notice-incr", JacksonUtil.toJson(notice));
        sseService.sendMsgAllAsync("notice-incr", JacksonUtil.toJson(notice));
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void toUser(Notice notice, List<Long> userIds) {
        AssertUtil.notEmpty(userIds, "请指定通知用户");
        notice.setRecipientType(NoticeRecipientTypeEnum.named);
        AssertUtil.isTrue(save(notice), I18n.build("通知失败"));
        List<NoticeRecipient> recipients = userIds.stream().map(x -> {
            NoticeRecipient recipient = new NoticeRecipient();
            recipient.setUserId(x);
            recipient.setNoticeId(notice.getId());
            recipient.setStatus(NoticeStatusEnum.unread);
            return recipient;
        }).collect(Collectors.toList());
        AssertUtil.isTrue(noticeRecipientService.saveBatch(recipients), "设置通知接收人失败");
        webSocketService.sendMsgByUserAsync("notice-incr", JacksonUtil.toJson(notice), userIds);
        sseService.sendMsgByUserAsync("notice-incr", JacksonUtil.toJson(notice), userIds);
    }

    /**
     * 监听websocket用户上线事件查询用户未读消息
     * @param event
     */
    @EventListener(WebSocketOpenEvent.class)
    public void onWebSocketOpen(WebSocketOpenEvent event) {
        IPage<NoticeVo> page = unreadNoticeList(UserContext.getUserId(), new Page<>(1, 10));
        SimplePageResult<NoticeVo> result = new SimplePageResult<>(page.getTotal(), page.getRecords());
        webSocketService.sendMsgByClient("notice", JacksonUtil.toJson(result), event.getClientId());
    }

    /**
     * 监听websocket用户上线事件查询用户未读消息
     * @param event
     */
    @EventListener(SseEvent.class)
    public void onSseOpen(SseEvent event) {
        IPage<NoticeVo> page = unreadNoticeList(UserContext.getUserId(), new Page<>(1, 10));
        SimplePageResult<NoticeVo> result = new SimplePageResult<>(page.getTotal(), page.getRecords());
        sseService.sendMsgByClient("notice", JacksonUtil.toJson(result), event.getClientId());
    }

    @Override
    public boolean removeByIds(Collection<?> list) {
        if (!super.removeByIds(list)) {
            return false;
        }
        // 删除用户关联
        noticeRecipientService.remove(
                Wrappers.<NoticeRecipient>lambdaQuery()
                        .in(NoticeRecipient::getNoticeId, list)
        );
        return true;
    }

    @Override
    public IPage<NoticeVo> unreadNoticeList(Long userId, IPage<NoticeVo> page) {
        QueryWrapper<NoticeVo> queryWrapper = Wrappers.<NoticeVo>query()
                .eq("status", NoticeStatusEnum.unread);
        return baseMapper.page(page, userId, queryWrapper);
    }

    @Override
    public PageResult<NoticeVo> unreadNotice(Long userId, Long webCount) {
        QueryWrapper<NoticeVo> queryWrapper = Wrappers.<NoticeVo>query()
                .eq("status", NoticeStatusEnum.unread);
        long dbCount = baseMapper.count(userId, queryWrapper);
        if (Objects.equals(webCount, dbCount)) {
            // 统计数量和前端已知数量相同说明没有新消息，无需更新前端显示
            return null;
        }
        // 数量不相同则更新前端消息显示
        IPage<NoticeVo> page = baseMapper.page(new Page<>(1, 10, false), userId, queryWrapper);
        return new PageResult<>(dbCount, page.getRecords());
    }

    @Override
    public PageResult<NoticeVo> userNoticeList(Long userId, PageQuery pageQuery, QueryParams params) {
        SqueryWapper<NoticeVo> squeryWapper = new SqueryWapper<>(NoticeVo.class);
        squeryWapper.setMapper(NoticeVo::getStatus, NoticeRecipient::getStatus);
        QueryWrapper<NoticeVo> queryWrapper = squeryWapper.listWrapper(params);
        IPage<NoticeVo> page = baseMapper.page(PageUtils.toPage(pageQuery), UserContext.getUserId(), queryWrapper);
        return PageUtils.toPageResult(page);
    }

    @Override
    public void removeUserNotice(Long userId, List<Long> ids) {
        // 对广播消息筛选出未读的消息
        List<NoticeRecipient> list = noticeRecipientService.list(
                Wrappers.<NoticeRecipient>lambdaQuery()
                        .select(NoticeRecipient::getNoticeId)
                        .in(NoticeRecipient::getNoticeId, ids)
                        .eq(NoticeRecipient::getUserId, userId)
        );
        // 更新未读广播消息为已读状态
        Set<Long> noticeIds = list.stream().map(NoticeRecipient::getNoticeId).collect(Collectors.toSet());
        List<NoticeRecipient> noticeRecipients = new ArrayList<>(ids.size());
        for (Long x : ids) {
            if (noticeIds.contains(x)) {
                continue;
            }
            NoticeRecipient recipient = new NoticeRecipient();
            recipient.setUserId(userId);
            recipient.setNoticeId(x);
            recipient.setReadTime(LocalDateTime.now());
            recipient.setStatus(NoticeStatusEnum.read);
            noticeRecipients.add(recipient);
        }
        noticeRecipientService.saveBatch(noticeRecipients);
        // 对指定人的消息直接删除
        noticeRecipientService.remove(
                Wrappers.<NoticeRecipient>lambdaQuery()
                        .eq(NoticeRecipient::getUserId, UserContext.getUserId())
                        .in(NoticeRecipient::getNoticeId, ids)
                        .exists("SELECT 1 FROM lg_msg_notice b WHERE b.id=lg_msg_notice_recipient.notice_id AND b.recipient_type = 1")
        );
        // 广播消息更新为删除状态
        noticeRecipientService.update(
                Wrappers.<NoticeRecipient>lambdaUpdate()
                        .eq(NoticeRecipient::getUserId, UserContext.getUserId())
                        .in(NoticeRecipient::getNoticeId, ids)
                        .exists("SELECT 1 FROM lg_msg_notice b WHERE b.id=lg_msg_notice_recipient.notice_id AND b.recipient_type = 0")
                        .set(NoticeRecipient::getStatus, 2)
        );
        // 推送消息
        pushMsgToUser(userId);
    }

    @Override
    public void updateRead(Long userId, Long noticeId) {
        Long id = noticeRecipientService.getObj(
                Wrappers.<NoticeRecipient>lambdaQuery()
                        .select(NoticeRecipient::getId)
                        .eq(NoticeRecipient::getNoticeId, noticeId)
                        .eq(NoticeRecipient::getUserId, userId),
                x -> Long.parseLong(x.toString())
        );
        if (id == null) {
            NoticeRecipient recipient = new NoticeRecipient();
            recipient.setUserId(userId);
            recipient.setNoticeId(noticeId);
            recipient.setReadTime(LocalDateTime.now());
            recipient.setStatus(NoticeStatusEnum.read);
            noticeRecipientService.save(recipient);
        } else {
            noticeRecipientService.update(
                    Wrappers.<NoticeRecipient>lambdaUpdate()
                            .set(NoticeRecipient::getStatus, NoticeStatusEnum.read)
                            .set(NoticeRecipient::getReadTime, LocalDateTime.now())
                            .eq(NoticeRecipient::getId, id)
            );
        }
        pushMsgToUser(userId);
    }

    @Override
    public Notice read(Long id) {
        Notice notice = getById(id);
        AssertUtil.notNull(notice, "数据不存在");
        updateRead(UserContext.getUserId(), id);
        return notice;
    }

    @Override
    public NoticeDetailVo detail(Long id) {
        Notice notice = getById(id);
        NoticeDetailVo vo = PojoUtil.copy(notice, NoticeDetailVo.class);
        List<NoticeRecipient> list = noticeRecipientService.listByNoticeId(id);
        vo.setRecipients(list);
        return vo;
    }

    private void pushMsgToUser(Long userId) {
        IPage<NoticeVo> page = unreadNoticeList(userId, new Page<>(1, 10));
        SimplePageResult<NoticeVo> result = new SimplePageResult<>(page.getTotal(), page.getRecords());
        webSocketService.sendMsgByUserAsync("notice", JacksonUtil.toJson(result), UserContext.getUserId());
        sseService.sendMsgByUserAsync("notice", JacksonUtil.toJson(result), UserContext.getUserId());
    }

}
